﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;

namespace Netfraction.RemoteMachine
{
	public abstract class Connection
	{
		protected Node mParentNode;
		protected Socket mSocket;
		private NetworkStream _stream;
		private SslStream _secureStream;
		private bool _isSecure;
		private bool _messageIncomplete;
		private string _incompleteMessageFrame;
		protected ConnectionState mState;
		protected ConnectionMode mConnectionMode;

		private const int STREAM_BUFFER_SIZE = 16384;

		private struct StreamState
		{
			public byte[] buffer;
		} 

		protected Connection(Node parentNode)
		{
			mParentNode = parentNode;
			mState = ConnectionState.Disconnected;
			mConnectionMode = ConnectionMode.Message;

			_incompleteMessageFrame = string.Empty;

			_secureStream = null;
			var secureStreamFeatures = parentNode.Features.Where(q => q.Key == "ADCS");
			if (secureStreamFeatures.Count() > 0)
				_isSecure = true;
			else
				_isSecure = false;
		}
		
		public ConnectionState State
		{
			get { return mState; }
			set
			{
				mState = value;
				if (value == ConnectionState.Data)
					mConnectionMode = ConnectionMode.Data;
				else
					mConnectionMode = ConnectionMode.Message;
			}
		}
		
		/// <summary>
		/// Return Local EndPoint
		/// </summary>
		public IPEndPoint LocalEndPoint
		{
			get
			{
				if (mSocket != null && mSocket.Connected)
					return mSocket.LocalEndPoint as IPEndPoint;
				else
					return null;
			}
		}

		/// <summary>
		/// Return Remote EndPoint
		/// </summary>
		public IPEndPoint RemoteEndPoint
		{
			get
			{
				return (mSocket != null && mSocket.Connected) ? mSocket.RemoteEndPoint as IPEndPoint : null;
			}
		}

		/// <summary>
		/// Called after the connection to the Node is established
		/// </summary>
		protected virtual void OnConnect()
		{
			LocalMachine.Client client = LocalMachine.Client.GetInstance();

			lock (client)
			{
				bool handled = false;
				foreach (Network.Protocol.ProtocolFeature f in client.Features.Values)
				{
					if (!handled)
						handled = f.HandleConnect(this.mParentNode);
				}
			}
		}

		/// <summary>
		/// Called after Disconnect
		/// </summary>
		protected abstract void OnDisconnect();

		/// <summary>
		/// Called after a message is Received 
		/// </summary>
		protected abstract void OnMessageReceive(Network.Protocol.ProtocolMessage message);

		/// <summary>
		/// Called after a message is Sent
		/// </summary>
		protected abstract void OnMessageSend(Network.Protocol.ProtocolMessage message);

		/// <summary>
		/// Called after array is Received 
		/// </summary>
		protected virtual void OnDataReceive(byte[] buffer, int offset, int receivedBytesCount)
		{
			return;
		}

		/// <summary>
		/// Called after array is Sent
		/// </summary>
		protected virtual void OnDataSend(byte[] buffer)
		{
			return;
		}

		/// <summary>
		/// Connect to the remote node.
		/// </summary>
		public void Connect(EndPoint endPoint)
		{
			// Initialize Socket 
			InitializeSocket(endPoint);

			// Being Async Socket Connect();
			BeginAsyncSocketConnect(endPoint);
		}

		/// <summary>
		/// Disconnect from the remote node.
		/// </summary>
		public void Disconnect()
		{
			if (_secureStream != null)
				_secureStream.Close();
			else if (_stream != null)
				_stream.Close(); // Close Stream
			else
				if (mSocket != null)
					mSocket.Close(); // Close Socket

			// Set Connection State
			mState = ConnectionState.Disconnected;

			// Call Handlers
			OnDisconnect();
		}

		/// <summary>
		/// Receive Message
		/// </summary>
		public void Receive(byte[] buffer, int receivedBytesCount)
		{
			if (receivedBytesCount == 0)
				return;

			int bytesLeftToProcess = receivedBytesCount;

			// TODO: move the switch statement outside the loop!!
			while (bytesLeftToProcess > 0)
			{
				switch (mConnectionMode)
				{
					case ConnectionMode.Message:
						// Process raw string into ProtocolMessages
						string messageFrame = string.Empty;
						/*if (receivedBytesCount > 250)
						{
							string tr = Network.Protocol.ProtocolMessage.DecodeMessage(buffer, 0, 250);
							Console.WriteLine(tr);
							if (tr.StartsWith("CSND"))
							{
								Console.WriteLine(tr);
							}
						}*/
						string tempStr = string.Empty;
						int messageLength = 0;
						int startOfMessage = receivedBytesCount - bytesLeftToProcess;
						int endOfMessage = receivedBytesCount;
						bool endFound = false;
						for (int i = startOfMessage; i < receivedBytesCount; i++)
						{
							if (buffer[i] == 0x0a)
							{
								endOfMessage = i;
								endFound = true;
								break;
							}
						}

						messageLength = endOfMessage - startOfMessage + (endFound ? 1 : 0);
						tempStr = Network.Protocol.ProtocolMessage.DecodeMessage(buffer, startOfMessage, messageLength);

						if (_messageIncomplete) // previous message was incomplete
						{
							messageFrame = _incompleteMessageFrame;
							_messageIncomplete = false;
							_incompleteMessageFrame = string.Empty;
						}

						if (!endFound)  // this message is incomplete since it doesn't end with a newline character
						{
							_messageIncomplete = true;

							_incompleteMessageFrame = messageFrame + tempStr;
							return;
						}
						else   // the entire message, or the remainder of it, is in this frame
						{
							messageFrame += tempStr;
						}
						bytesLeftToProcess -= messageLength;
						List<Network.Protocol.ProtocolMessage> protocolMessages = Network.Protocol.ProtocolMessage.ProcessIncomingMessages(messageFrame);

						// Loop All ProtocolMessages
						foreach (Network.Protocol.ProtocolMessage protocolMessage in protocolMessages)
						{
							// Notify Handlers
							OnMessageReceive(protocolMessage);
						}
						break;
					#region Old Code
					/*case ConnectionMode.Message:
						// Process raw string into ProtocolMessages
						string messageFrame = string.Empty;
						string tempStr = Protocol.ProtocolMessage.DecodeMessage(buffer, receivedBytesCount);
						int lastNewLine = 0;

						if (_messageIncomplete) // previous message was incomplete
						{
							messageFrame = _incompleteMessageFrame;
							_messageIncomplete = false;
							_incompleteMessageFrame = string.Empty;
						}

						if (buffer[receivedBytesCount - 1] != 0x0a)   // 0x0a = '\n'. this message is incomplete since it doesn't end with a newline character
						{
							_messageIncomplete = true;

							lastNewLine = tempStr.LastIndexOf('\n');    // get the last newline character
							if (lastNewLine > 0)    // the message contains a newline character, so process the complete protocol messages and save the incomplete one
							{
								messageFrame += tempStr.Substring(0, lastNewLine);

								_incompleteMessageFrame = tempStr.Substring(lastNewLine + 1);
							}
							else    // the message has no newline characters so it, and the previous message, are incomplete
							{
								_incompleteMessageFrame = messageFrame + tempStr;
								return;
							}
						}
						else   // the entire message, or the remainder of it, is in this frame
						{
							messageFrame += tempStr;
						}

						List<Protocol.ProtocolMessage> protocolMessages = Protocol.ProtocolMessage.ProcessIncomingMessages(messageFrame);

						// Loop All ProtocolMessages
						foreach (Protocol.ProtocolMessage protocolMessage in protocolMessages)
						{
							// Notify Handlers
							OnMessageReceive(protocolMessage);
						}
						break;*/
					#endregion
					case ConnectionMode.Data:
						//System.Diagnostics.Debug.WriteLine(String.Format("receiving array! {0}  {1}", receivedBytesCount - bytesLeftToProcess, receivedBytesCount));
						OnDataReceive(buffer, receivedBytesCount - bytesLeftToProcess, bytesLeftToProcess);
						bytesLeftToProcess -= bytesLeftToProcess;
						break;
					case ConnectionMode.Extension:
						break;
					default:
						break;
				}
			}
		}

		/// <summary>
		/// Send Message
		/// </summary>
		public void Send(string message)
		{
			if (message.Length == 0)
				return;

			if (mSocket != null)
			{
				// Process Message Into Individual Messages - I'm not sure why this would happen in the Send method?
				List<Network.Protocol.ProtocolMessage> protocolMessages = Network.Protocol.ProtocolMessage.ProcessIncomingMessages(message);

				// Loop All Messages
				foreach (Network.Protocol.ProtocolMessage protocolMessage in protocolMessages)
				{
					// Notify Handlers
					OnMessageSend(protocolMessage);

					byte[] buffer = null;

					// Encode Message
					buffer = Network.Protocol.ProtocolMessage.EncodeMessage(protocolMessage.ToString());

					// Begin Async Stream Write
					BeginAsyncStreamWrite(buffer);
				}
			}
		}

		public void Send(byte[] buffer)
		{
			BeginAsyncStreamWrite(buffer);
		}

		/// <summary>
		/// Initialize Socket
		/// </summary>
		private void InitializeSocket(EndPoint endPoint)
		{
			try
			{
				// Initialize Socket
				mSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
			}
			catch (ObjectDisposedException)
			{
				return;
			}
			catch (SocketException)
			{
				// Log
				//log.ErrorException("Error initializing socket.", ex);
			}
		}

		/// <summary>
		/// Start Asynchronous Socket Connection
		/// </summary>
		protected void BeginAsyncSocketConnect(EndPoint endPoint)
		{
			try
			{
				// Being Async Socket Connection
				mSocket.BeginConnect(endPoint, new AsyncCallback(EndAsyncSocketConnect), null);
			}
			catch (ObjectDisposedException)
			{
				return;
			}
			catch (SocketException)
			{
				// Log
				/*log.ErrorException("Error connecting to hub.", ex);

				// Raise Error
				RaiseError(new NodeEventArgs(ex));
				*/
				// Disconnect 
				Disconnect();

				// Return
				return;
			}
		}

		/// <summary>
		/// End Asynchronous Socket Connection
		/// </summary>
		private void EndAsyncSocketConnect(IAsyncResult asyn)
		{
			try
			{
				// End Socket Accept
				mSocket.EndConnect(asyn);
			}
			catch (ObjectDisposedException)
			{
				return;
			}
			catch (SocketException)
			{
				// Log
				/*log.ErrorException("Error connecting to hub.", ex);

				// Raise Error
				RaiseError(new NodeEventArgs(ex));
				*/
				// Disconnect 
				Disconnect();

				// Return
				return;
			}
			//Console.WriteLine(_socket.LocalEndPoint);
			// Create NetworkStream
			_stream = new NetworkStream(mSocket, true);
			if (_isSecure)
			{
				_secureStream = new SslStream(_stream, false,
				   new RemoteCertificateValidationCallback(Netfraction.Network.Protocol.ADCS.Security.Verification.RemoteCertificateValidationCallback),
				   new LocalCertificateSelectionCallback(Netfraction.Network.Protocol.ADCS.Security.Selection.LocalCertificateSelectionCallback));
				_secureStream.AuthenticateAsClient("", new System.Security.Cryptography.X509Certificates.X509CertificateCollection(), System.Security.Authentication.SslProtocols.Tls , true);
			}

			// Set Connection State
			mState = ConnectionState.Protocol;

			// Begin Async Stream Read
			BeginAsyncStreamRead();

			// Call Handlers 
			OnConnect();
		}

		/// <summary>
		/// Start Asynchronous Socket Connection
		/// </summary>
		protected void BeginAsyncSocketAccept()
		{
			try
			{
				// Being Async Socket Connection
				mSocket.BeginAccept(new AsyncCallback(EndAsyncSocketAccept), null);
			}
			catch (ObjectDisposedException)
			{
				return;
			}
			catch (SocketException)
			{
				// Disconnect 
				Disconnect();

				return;
			}
		}

		/// <summary>
		/// End Asynchronous Socket Connection
		/// </summary>
		private void EndAsyncSocketAccept(IAsyncResult asyn)
		{
			try
			{
				// End Socket Accept
				mSocket = mSocket.EndAccept(asyn);
			}
			catch (ObjectDisposedException)
			{
				return;
			}
			catch (SocketException)
			{
				// Disconnect 
				Disconnect();

				return;
			}

			// Create NetworkStream
			_stream = new NetworkStream(mSocket, true);
			if (_isSecure)
				_secureStream = new SslStream(_stream, false);//, null, null)

			// Set Connection State
			mState = ConnectionState.Protocol;

			// Begin Async Stream Read
			BeginAsyncStreamRead();

			// Call Handlers 
			OnConnect();
		}

		/// <summary>
		/// Start Asynchronous Stream Read
		/// </summary>
		private void BeginAsyncStreamRead()
		{
			StreamState streamState = new StreamState();
			streamState.buffer = new byte[STREAM_BUFFER_SIZE];

			try
			{
				if (_isSecure && _secureStream != null)
					_secureStream.BeginRead(streamState.buffer, 0, STREAM_BUFFER_SIZE, new AsyncCallback(EndAsyncStreamRead), streamState);
				else
					_stream.BeginRead(streamState.buffer, 0, STREAM_BUFFER_SIZE, new AsyncCallback(EndAsyncStreamRead), streamState);
			}
			catch (Exception)
			{
				Disconnect();
			}
		}

		/// <summary>
		/// End Asynchronous Stream Read
		/// </summary>    
		private void EndAsyncStreamRead(IAsyncResult asyn)
		{
			byte[] buffer = null;
			int bytesReceived = 0;

			try
			{
				bytesReceived = _isSecure ? _secureStream.EndRead(asyn) : _stream.EndRead(asyn);
				buffer = ((StreamState)asyn.AsyncState).buffer;
			}
			catch (Exception)
			{
				Disconnect();
			}

			// Check number of bytes recieved
			if (bytesReceived > 0)
			{
				// Recieve Data From Buffer
				Receive(buffer, bytesReceived);

				// loop stream reading
				BeginAsyncStreamRead();
			}
			else
			{
				Disconnect();
			}
		}

		/// <summary>
		/// Start Asynchronous Stream Write
		/// </summary>    
		private void BeginAsyncStreamWrite(byte[] buffer)
		{
			// Initialize StreamState object
			StreamState streamState = new StreamState();
			streamState.buffer = buffer;

			try
			{
				// Begin Async Stream Read
				if (_isSecure)
					_secureStream.BeginWrite(streamState.buffer, 0, streamState.buffer.Length, new AsyncCallback(EndAsyncStreamWrite), streamState);
				else
					_stream.BeginWrite(streamState.buffer, 0, streamState.buffer.Length, new AsyncCallback(EndAsyncStreamWrite), streamState);
			}
			catch (Exception)
			{
				// Log
				/*log.ErrorException("Error reading NetworkStream", ex);

				// Raise Error
				RaiseError(new NodeEventArgs(ex));
				*/
				// Disconnect
				Disconnect();
			}
		}

		/// <summary>
		/// End Asynchronous Stream Write
		/// </summary>    
		private void EndAsyncStreamWrite(IAsyncResult asyn)
		{
			try
			{
				// End Async Stream Write    
				if (_isSecure)
					_secureStream.EndWrite(asyn);
				else
					_stream.EndWrite(asyn);
			}
			catch (Exception)
			{
				// Log
				/*log.ErrorException("Error reading NetworkStream", ex);

				// Raise Error
				RaiseError(new NodeEventArgs(ex));
				*/
				// Disconnect
				Disconnect();
			}
		}
	}
}
